package jfood.chef;

import gnu.prolog.term.AtomTerm;
import gnu.prolog.term.CompoundTerm;
import gnu.prolog.term.Term;
import gnu.prolog.term.VariableTerm;
import gnu.prolog.vm.Environment;
import gnu.prolog.vm.Interpreter;
import gnu.prolog.vm.Interpreter.Goal;
import gnu.prolog.vm.PrologCode;
import gnu.prolog.vm.PrologException;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
import java.util.StringTokenizer;

import jfood.ontology.meal.Ingredient;
import jfood.ontology.meal.Meal;
import jfood.ontology.meal.Recipe;
import jfood.ontology.meal.Result;

public class DatabaseReader{
	
	public DatabaseReader(final String databasePath){
		environment_ = new Environment();

		environment_.ensureLoaded(AtomTerm.get(databasePath));

		final String RULES_PATH = "rules.pro";
		environment_.ensureLoaded(AtomTerm.get(RULES_PATH));

		interpreter_ = environment_.createInterpreter();
		environment_.runInitialization(interpreter_);
	}
	
	public Meal getMeal(final String mealName){
		
		ArrayList<Ingredient> ingredients = new ArrayList<Ingredient>();
		Recipe recipe = new Recipe("");

		final boolean ingredientsGot = getIngredientList(mealName, ingredients);
		final boolean recipeGot = getRecipe(mealName, recipe);
		
		Meal meal = null;
		if(ingredientsGot || recipeGot)
		{
			meal = new Meal(mealName);
		}
		if(ingredientsGot)
		{
			meal.setIngredients(ingredients);
		}
		if(recipeGot)
		{
			meal.setRecipe(recipe);
		}
		
		return meal;
	}

	public String getMealNameByStems(final String stems){
		Term stemTerm = buildTermList(stems);
		
		VariableTerm mealName = new VariableTerm("MealName");

		Term[] args = {stemTerm, mealName};

		boolean isSuccess = runOnce("get_meal_by_stem", args);


		String retValue = "";
		
		if(isSuccess)
		{
			Term mealDeref  = mealName.dereference();

			String tempString = deserializeDerefePrologOutput(
					mealDeref, "");

			retValue = tempString;
		}
		return retValue;
	}
	
	public ArrayList<Meal> getMealWithGivenIngredients(ArrayList<Ingredient> ingredientsArray){

		ArrayList<Meal> mealList = new ArrayList<Meal>();

		for (Ingredient ing : ingredientsArray) {
			String ingredientName = ing.getName();
			Term ingredientTerm = buildTermList(ingredientName);

			VariableTerm mealName = new VariableTerm("MealName");

			Term[] args = {ingredientTerm , mealName};

			CompoundTerm term = new CompoundTerm(AtomTerm.get("what_can_i_do"),
					args);
			
			Goal interpreterGoal = interpreter_.prepareGoal(term);
			
			ArrayList<String> mealNamesList = new ArrayList<String>();
			while (true) {
				boolean isSuccess = execute(interpreterGoal);
				
				if(isSuccess)
				{
					Term mealDeref  = mealName.dereference();
					
					String tempString = deserializeDerefePrologOutput(
							mealDeref, "");
					
					mealNamesList.add(tempString);
				}
				else
				{
					break;
				}
			}
			for(String name: mealNamesList)
			{
				Meal meal = getMeal(name);
				if(meal != null)
				{
					mealList.add(meal);
				}
			}
		}
		return mealList;
	}
	public ArrayList<Meal> getMealWithGivenOneIngredientsStem(String ingredientsArray)
	{
		ArrayList<Meal> mealList = new ArrayList<Meal>();

		Term ingredientTerm = buildTermList(ingredientsArray);

		VariableTerm mealName = new VariableTerm("MealName");

		Term[] args = {ingredientTerm , mealName};

		CompoundTerm term1 = new CompoundTerm(AtomTerm.get("what_can_i_do_stem"),
				args);
		
		System.out.println(term1.toString());

		Goal interpreterGoal = interpreter_.prepareGoal(term1);

		ArrayList<String> mealNamesList = new ArrayList<String>();
		while (true) {
			boolean isSuccess = execute(interpreterGoal);

			if(isSuccess)
			{
				Term mealDeref  = mealName.dereference();

				String tempString = deserializeDerefePrologOutput(
						mealDeref, "");

				mealNamesList.add(tempString);
			}
			else
			{
				break;
			}
		}

		for(String name: mealNamesList)
		{
			Meal meal = getMeal(name);
			if(meal != null)
			{
				mealList.add(meal);
			}
		}
		return mealList;
	}
	public ArrayList<Meal> getMealWithGivenIngredientsStem(ArrayList<String> ingredientsArray)
	{
		ArrayList<ArrayList<Meal>> listOfMealList = new ArrayList<ArrayList<Meal>>();
		for(String ingredient: ingredientsArray)
		{
			ArrayList<Meal> mealList = getMealWithGivenOneIngredientsStem(ingredient);
			listOfMealList.add(mealList);
		}

		ArrayList<Meal> commonMealList = new ArrayList<Meal>();
		if(listOfMealList.isEmpty() == false)
		{
			commonMealList = listOfMealList.get(0);
			for(int i = 1; i<listOfMealList.size();i++)
			{
				commonMealList = getCommonFromTwoList(commonMealList, listOfMealList.get(i));
			}
		}
		
		for(Meal meal: commonMealList)
		{
			System.out.println(meal.getName());
		}
		
		return commonMealList;
	}

	ArrayList<Meal> getCommonFromTwoList(ArrayList<Meal> list1, ArrayList<Meal> list2)
	{
		ArrayList<Meal> mealList = new ArrayList<Meal>();
		
		for(Meal m1: list1)
		{
			for(Meal m2: list2)
			{
				String a1 = m1.getName();
				String a2 = m2.getName();
				
				if(a1.equals(a2))
				{
					mealList.add(m1);
					break;
				}
			}
		}
		
		return mealList;
		
	}
	public boolean getIngredientList(final String mealName, ArrayList<Ingredient> ingredients) {

		VariableTerm fullIngredientsList = new VariableTerm(
				"FullIngredientsList");
		Term mealTerm = buildTermList(mealName);
		Term[] args1 = { mealTerm, fullIngredientsList };

		final boolean isOk = runOnce("ingredients", args1);

		ArrayList<String> ingredientsNames = new ArrayList<String>();

		if (isOk == true) {
			Term fullIngredientsDeref = fullIngredientsList.dereference();

//			System.out.println(fullIngredientsDeref.toString());
			
			String tempString = deserializeDerefePrologOutput(
					fullIngredientsDeref, "|");

			StringTokenizer tokenizer = new StringTokenizer(
					tempString.toString(), "|");

			while (tokenizer.hasMoreTokens()) {
				ingredientsNames.add(tokenizer.nextToken());
			}
		}

		ingredients.addAll(Ingredient.convertNamesToIngredients(ingredientsNames));

		return isOk;
	}

	public boolean getRecipe(final String mealName, Recipe recipe) {

		VariableTerm recipeTerm = new VariableTerm("Recipe");
		Term mealTerm = buildTermList(mealName);

		Term[] args1 = { mealTerm, recipeTerm };
		final boolean isOk = runOnce("recipe", args1);

		String recipeContent = "";

		if (isOk == true) {
			Term recipeDeref = recipeTerm.dereference();

			recipeContent = deserializeDerefePrologOutput(recipeDeref, "|");
		}

		recipe.setContent(recipeContent);
		
		return isOk;
	}

	private String deserializeDerefePrologOutput(Term output, String splitter) {
		String tempString = output.toString();

		tempString = tempString.replace("\\x20\\", " ");
		tempString = tempString.replace("]],[[", splitter);
		tempString = tempString.replace("],[", " ");
		tempString = tempString.replaceAll("[\\[\\]\\']", "");

		return tempString;
	}

	public Result checkElement(final String name) {
		final String elementName = name;
		final String elementsList[] = elementName.split(" ");

		final Term term = buildTermList(elementsList);

//		System.out.println(term.toString());

		final Result result = checkElement(term);

		return result;
	}

	public Result checkElement(final Term term) {
		Result result = new Result();

		result.isIngredient = isIngredient(term);
		result.isMeal = isMeal(term);
		result.isPartOfIngredient = isPartOfIngredient(term);
		result.isPartOfMeal = isPartOfMeal(term);
		result.isPartOf = isPartOf(term);

		return result;
	}

	public Result checkElementWithStems(final String name) {
		final String elementName = name;
		final String elementsList[] = elementName.split(" ");

		final Term term = buildTermList(elementsList);

		final Result result = checkElementWithStems(term);

		return result;
	}

	
	public Result checkElementWithStems(final Term term) {
		Result result = new Result();

		result.isIngredient = isIngredient(term);
		result.isMeal = isMeal(term);
		result.isPartOfIngredient = isPartOfIngredient(term);
		result.isPartOfMeal = isPartOfMeal(term);
		result.isPartOf = isPartOf(term);
		result.isPartOfIngredientStem = isPartOfIngredientStem(term);
		result.isIngredientStem = isIngredientStem(term);
		result.isPartOfMealStem = isPartOfMealStem(term);
		result.isMealStem = isMealStem(term);

		return result;
	}
	
	private boolean isPartOfMealStem(final Term term) {
		final String IS_MEAL_PRO = new String("is_part_of_meal_stem");

		final boolean isMeal = runOnce(IS_MEAL_PRO, term);

		return isMeal;
	}
	
	private boolean isMealStem(final Term term) {
		final String IS_MEAL_STEM_PRO = new String("is_meal_stem");

		final boolean isMeal = runOnce(IS_MEAL_STEM_PRO, term);

		return isMeal;
	}
	
	private boolean isMeal(final Term term) {
		final String IS_MEAL_PRO = new String("is_meal");

		final boolean isMeal = runOnce(IS_MEAL_PRO, term);

		return isMeal;
	}

	private boolean isIngredient(final Term term) {
		final String IS_INGREDIENT_PRO = new String("is_ingredient");

		final boolean isIngredient = runOnce(IS_INGREDIENT_PRO, term);

		return isIngredient;
	}
	private boolean isIngredientStem(final Term term) {
		final String IS_INGREDIENT_STEM_PRO = new String("is_ingredient_stem");

		final boolean isIngredient = runOnce(IS_INGREDIENT_STEM_PRO, term);

		return isIngredient;
	}

	private boolean isPartOf(final Term term) {
		final String IS_PART_OF_PRO = new String("is_part_of");

		final boolean isPartOf = runOnce(IS_PART_OF_PRO, term);

		return isPartOf;
	}

	private boolean isPartOfIngredient(final Term term) {
		final String IS_PART_OF_INGREDIENT_PRO = new String(
				"is_part_of_ingredient");

		final boolean isPartOf = runOnce(IS_PART_OF_INGREDIENT_PRO, term);

		return isPartOf;
	}
	
	private boolean isPartOfIngredientStem(final Term term) {
		final String IS_PART_OF_INGREDIENT_STEM_PRO = 
				"is_part_of_ingredient_stem";

		final boolean isPartOf = runOnce(IS_PART_OF_INGREDIENT_STEM_PRO, term);

		return isPartOf;
	}

	private boolean isPartOfMeal(final Term term) {
		final String IS_PART_OF_MEAL_PRO = new String("is_part_of_meal");

		final boolean isPartOf = runOnce(IS_PART_OF_MEAL_PRO, term);

		return isPartOf;
	}
	
	private Term buildTerm(String termStr) {
		List<Term> terms = new LinkedList<Term>();
		terms.add(AtomTerm.get(termStr));
		return CompoundTerm.getList(terms);
	}

//	private Term buildTerm(Term term) {
//		List<Term> terms = new LinkedList<Term>();
//		terms.add(term);
//		return CompoundTerm.getList(terms);
//	}

	private Term buildTermList(String str) {
		StringTokenizer tokenizer = new StringTokenizer(str);
		List<Term> terms = new LinkedList<Term>();
		while (tokenizer.hasMoreElements()) {
			terms.add(buildTerm(tokenizer.nextToken()));
		}
		return CompoundTerm.getList(terms);
	}

	private Term buildTermList(String[] str) {
		if (str != null) {
			List<Term> terms = new LinkedList<Term>();
			for (String string : str) {
				terms.add(buildTerm(string));
			}
			return CompoundTerm.getList(terms);
		}
		return null;
	}
	
	private Term buildTermListFromStringList(ArrayList<String> strArray) {
		List<Term> terms = new LinkedList<Term>();
		for (String s: strArray)
		{
			terms.add(buildTermList(s));
		}
		return CompoundTerm.getList(terms);
	}

	private boolean runOnce(final String goal, final Term term) {
		final Term[] terms = { term };

		return runOnce(goal, terms);
	}

	private boolean execute(Goal goal) {
		int result = PrologCode.FAIL;
		
		try {
			result = interpreter_.execute(goal);
		} catch (PrologException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		boolean retValue = false;
		
		if(result == PrologCode.SUCCESS || result == PrologCode.SUCCESS_LAST)
		{
			retValue = true;
		}
		return retValue;
	}
	
	private boolean runOnce(final String goal, final Term[] terms) {
		final CompoundTerm goalTerm = new CompoundTerm(AtomTerm.get(goal),
				terms);

//		System.out.println(goalTerm.toString());
		
		int result = PrologCode.FAIL;

		try {
			result = interpreter_.runOnce(goalTerm);
		} catch (PrologException e) {
			e.printStackTrace();
		}

		boolean retValue = false;

		if (result == PrologCode.SUCCESS || result == PrologCode.SUCCESS_LAST) {
			retValue = true;
		}

		return retValue;
	}

	public static void main(String[] argv) {
		final String CHECK = "check";
		final String CHECK_STEM = "check_stem";
		
		final String RECIPE = "recipe";
		final String INGREDIENTS = "ingredients";
		final String MEAL = "meal";
		final String PREP = "prep";
		final String INGREDIENTS_STEM = "ingredients_stem";
		final String GET_MEAL = "get_meal";
		
		
		Scanner sc = new Scanner(System.in);
		String line;
		final String TEST_FILE = "chef.pro";
		DatabaseReader checker = new DatabaseReader(TEST_FILE);
		while (sc.hasNextLine()) {
			
			line = sc.nextLine();
			System.out.println("line " + line);
			String[] elements = line.split(" ");
			
			if (elements[0].compareToIgnoreCase(CHECK) == 0) {
				
				Result result = checker.checkElement(line.substring(CHECK.length() + 1));
				
				System.out.println(result.toString());
				
				
			} else if (elements[0].compareToIgnoreCase(CHECK_STEM) == 0) {
				
				Result result = checker.checkElementWithStems(line.substring(CHECK_STEM.length() + 1));
				
				System.out.println(result.toString());
				
				
			}else if (elements[0].compareToIgnoreCase(INGREDIENTS) == 0) {
				
				ArrayList<Ingredient> ingredients = new ArrayList<Ingredient>(); 
				checker.getIngredientList(line.substring(INGREDIENTS.length() + 1), ingredients);
				
				for (Ingredient i : ingredients) {
					System.out.println(i.getName());
				}
				
			} else if (elements[0].compareToIgnoreCase(RECIPE) == 0) {
				
				Recipe recipe = new Recipe("");
				checker.getRecipe(line.substring(RECIPE.length() + 1), recipe);
				
				System.out.println(recipe.getContent());
			}else if (elements[0].compareToIgnoreCase(MEAL) == 0) {
			
				Meal meal = checker.getMeal(line.substring(MEAL.length() + 1));

				if(meal != null)
				{
					System.out.println(meal.toString());
				}

			}else if (elements[0].compareToIgnoreCase(PREP) == 0) {

				ArrayList<String> ingredients = new ArrayList<String>();
				ingredients.add("jajko");
//				ingredients.add("ziele angielski");
				ingredients.add("maka");
				checker.getMealWithGivenIngredientsStem(ingredients);
			}else if (elements[0].compareToIgnoreCase(INGREDIENTS_STEM) == 0) {
				
				ArrayList<Ingredient> ingredients = new ArrayList<Ingredient>(); 
				checker.getIngredientList(line.substring(INGREDIENTS_STEM.length() + 1), ingredients);
				
				for (Ingredient i : ingredients) {
					System.out.println(i.getName());
				}
				
			}else if (elements[0].compareToIgnoreCase(GET_MEAL) == 0) {
				
				String mealName = checker.getMealNameByStems(line.substring(GET_MEAL.length() + 1));
				if(mealName != null)
				{
					System.out.println(mealName);
				}
			}

		}
		sc.close();
	}

	private Environment environment_;
	private Interpreter interpreter_;
}
